#!/usr/bin/env dart
// ignore_for_file: avoid_print, constant_identifier_names

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:http_parser/http_parser.dart';

/// 上传 Apk 的服务器的相关配置
const SERVER_IP = '154.12.20.37';
const WAN_SERVER_IP = '154.12.20.37';

///  企业微信相关文档：https://work.weixin.qq.com/api/doc/90000/90136/91770
const WECHAT_WEBHOOK_URL = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send';
const WECHAT_TOKEN = '7abe9e0e-d5a4-4938-af16-33a8a6987a0c';

/// adb 设备名称和 id 的映射
const adbIdNameMap = {
  'fjy': 'adb-8H99VCLNJRRGZX9D-A3hLof._adb-tls-connect._tcp',
};

/// json编码器，用于格式化
const ENCODER = JsonEncoder.withIndent('  ');

/// 渠道
late final String? channel;

/// app名称
late final String? appName;

/// 需要安装并启动的设备列表
late final List<String>? launchDevices;

/// 包名，仅用作启动应用，不影响编译
late final String? pkgId;

/// 是否输出详细
late final bool verbose;

/// 是否发送企业微信消息
late final bool noDing;

/// 是否允许在终端选择配置
late final bool selectProfile;

/// 配置文件名称
late final String? profileName;

/// 当前文件夹名称
late final String curDirName;

/// IDEA配置
late final Profile? profile;

/// 更新日志
String updateLog = '*_*';

void main(List<String> args) async {
  await parseArgs(args);
  await check();
  await buildApk();
}

/// 解析命令行参数
Future<void> parseArgs(List<String> args) async {
  if (args.contains('--help') || args.contains('-h')) {
    print('请查看文档：./ci/build_ci.md');
    exit(0);
  }

  final params = <String, String?>{};
  for (var i = 0; i < args.length;) {
    final arg = args[i];
    if (arg.startsWith('-')) {
      if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
        params[arg] = args[i + 1];
        i += 2;
      } else {
        params[arg] = null;
        i++;
      }
    } else {
      throw ArgumentError('无效参数: $arg');
    }
  }

  verbose = params.containsKey('--verbose') || params.containsKey('-v');
  selectProfile = params.containsKey('--no-select-profile') || params.containsKey('-nsp');
  profileName = params['--profile-name'] ?? params['-pn'];

  profile = !selectProfile ? await Profile.selectFromLocal() : null;

  channel = params['--channel'] ?? params['-c'] ?? profile?.chan;
  appName = params['--app-name'] ?? params['-n'] ?? profile?.appName;
  launchDevices = (params['--launch-app-devices'] ?? params['-ld'])?.split(',');
  pkgId = params['--pkg'] ?? params['-p'] ?? await PkgInfo.pkgId;
  noDing = params.containsKey('--no-ding') || params.containsKey('-nd');
}

/// 构建前检查
Future<void> check() async {
  curDirName = Directory.current.path.split(Platform.pathSeparator).last;

  /// 如果当前目录下无 pubspec.yaml 文件，说明不是 flutter 项目
  final isAtProjRoot = await File('pubspec.yaml').exists();
  if (!isAtProjRoot) {
    print('请返回 flutter 项目根目录');
    exit(1);
  }
}

/// 开始构建
Future<void> buildApk() async {
  final time = DateTime.now();

  await flutterBuild();

  const apkPath = 'build/app/outputs/flutter-apk/app-arm64-v8a-release.apk';
  final apkFile = File(apkPath);
  if (!await apkFile.exists()) {
    print('找不到apk: $apkPath');
    exit(1);
  }

  final size = await apkFile.length() / 1024 / 1024;
  final version = await PkgInfo.version;
  final buildTime = fmtTime(time);

  /// 加入时间，否则会覆盖
  final newFileName = '$appName-$buildTime-$version.apk';

  final results = <String, dynamic>{};

  /// 输入更新日志
  // 判断当前环境为Windows还是Mac
  String? updateLog;
  if (Platform.isWindows) {
    print('当前为windows,请输入更新日志:');
    updateLog = await powerShellWrite(hide: '请输入更新日志:') ?? '';
  } else if (Platform.isMacOS) {
    print('当前为Mac');
    stdout.write('请输入更新日志：');
    updateLog = stdin.readLineSync(encoding: utf8) ?? '暂无';
  }
  results['更新日志'] = updateLog;

  print('请输入是否传蒲公英,否上传到alist(y or n),默认为n:',);
  String input =  stdin.readLineSync(encoding: utf8) ?? 'n';
  if(input.toLowerCase() == 'n' || input.toLowerCase() == '') {
    print('不长传蒲公英');
    await upload(newFileName, apkFile);


    results['时间'] = buildTime;
    results['名称'] = appName;
    final remotePath = '/$curDirName/${profile?.name}';
    final appUrl = 'http://$SERVER_IP:5244$remotePath/$newFileName';
    final wanAppUrl = 'http://$WAN_SERVER_IP:5244$remotePath/$newFileName';
    results['链接'] = '[内网](${Uri.encodeFull(appUrl)}) | [外网](${Uri.encodeFull(wanAppUrl)})';
    results['大小'] = '${size.toStringAsFixed(2)}MB';
    results['版本'] = version;

    if (launchDevices != null) {
      print('安装并启动');
      final errs = <String>[];
      for (final device in launchDevices!) {
        final deviceId = adbIdNameMap[device];
        if (deviceId == null) {
          errs.add('设备 $device 未找到');
          continue;
        }

        final result = await installApp(deviceId: deviceId, apkPath: apkPath);
        if (result.exitCode != 0) {
          errs.add('安装到 $device 失败: ${result.stderr}');
        }

        final result2 = await launchApp(deviceId: deviceId);
        if (result2.exitCode != 0) {
          errs.add('启动 $device 失败: ${result2.stderr}');
        }

        final launchResult = results['部署状态'] as Map? ?? {};
        results['部署状态'] = {
          device: 'ok',
          ...launchResult,
        };
      }

      if (errs.isNotEmpty) {
        print(errs.join('\n'));
      }
    }

    print(results);

    await WeChat.send(data: results);

    print('上传完成');

  } else {
    await AppUploader.getCOSToken(path: apkPath,updateDescription: updateLog);
  }
}


/// PowerShell文件写入并返回内容
Future<String?> powerShellWrite({String? hide}) async {
  // 生成临时文件路径
  final String tempFilePath = '${Directory.systemTemp.path}\\input_data.txt';
  // 生成 PowerShell 命令 - 读取输入的数据，并写入临时文件
  final String powershellCommand = r'$InputData = Read-Host "' + (hide ?? '请输入内容') + '"; \$InputData | Out-File -FilePath "' + tempFilePath + '" -Encoding utf8';
  // 启动 PowerShell 进程，并将输入的数据写入临时文件
  final process = await Process.start(
    'powershell.exe',
    ['-Command', powershellCommand],
    mode: ProcessStartMode.inheritStdio,
  );
  // 等待完成
  await process.exitCode;
  // 读取临时文件内容
  return await File(tempFilePath).readAsStringSync();
}

// 获取当前项目选择的flutter路径
String? getFlutterSdkPath() {
  final file = File('android/local.properties');
  if (!file.existsSync()) return null;

  final properties = file.readAsLinesSync();
  for (var line in properties) {
    if (line.startsWith('flutter.sdk=')) {
      final path = line.split('=')[1].trim();
      return '$path/bin/flutter';
    }
  }
  return null;
}

Future<void> flutterBuild() async {
  // 获取当前项目选择的flutter路径
  final flutterBin = getFlutterSdkPath() ?? 'flutter';
  final buildArgs = [
    'build',
    'apk',
    '--release',
    if (channel != null) '--dart-define=APP_INSTALL_CHANNEL=$channel',
    if (appName != null) '--dart-define=APP_NAME=$appName',
    if (verbose) '--verbose',
    '--split-per-abi'
  ];
  print(buildArgs);
  final buildP = await Process.start(
    flutterBin,
    buildArgs,
    runInShell: true,
  );
  final stderrBuf = StringBuffer();
  final stdoutBuf = StringBuffer();
  buildP.stdout.transform(utf8.decoder).listen(stdoutBuf.writeAndPrint);
  buildP.stderr.transform(utf8.decoder).listen(stderrBuf.writeAndPrint);
  if (await buildP.exitCode != 0) {
    print('构建失败: ${stderrBuf.toString()}');
    exit(1);
  }
  stderrBuf.clear();
  stdoutBuf.clear();
}

Future<void> upload(String newFileName, File apkFile) async {
  print('上传到服务器(网速较慢,1m/s,耐心等待)');

  final resp = await Dio().post(
    'http://$SERVER_IP:8080/upload',
    queryParameters: {
      'filename': newFileName,
      'proj': curDirName,
      'dir': profile?.name,
    },
    data: FormData.fromMap({
      'file': MultipartFile.fromBytes(
        await apkFile.readAsBytes(),
        contentType: MediaType('application', 'vnd.android.package-archive'),
      ),
    }),
  );
  if (resp.statusCode != 200) {
    print('上传失败: ${resp.data}');
    exit(1);
  }
}

/// 安装到手机
Future<ProcessResult> installApp({
  required String deviceId,
  required String apkPath,
}) async {
  return await Process.run(
    'adb',
    [
      '-s',
      deviceId,
      'install',
      '-r',
      apkPath,
    ],
    runInShell: true,
  );
}

/// 调用 adb 启动应用
Future<ProcessResult> launchApp({required String deviceId}) async {
  return await Process.run(
    'adb',
    [
      '-s',
      deviceId,
      'shell',
      'am',
      'start',
      '-n',
      '$pkgId/.MainActivity',
    ],
    runInShell: true,
  );
}

extension StrBufX on StringBuffer {
  void writeAndPrint(Object? obj) {
    write(obj);
    print(obj);
  }
}

String fmtTime(DateTime time) {
  final month = time.month.toString().padLeft(2, '0');
  final day = time.day.toString().padLeft(2, '0');
  final hour = time.hour.toString().padLeft(2, '0');
  final minute = time.minute.toString().padLeft(2, '0');
  final second = time.second.toString().padLeft(2, '0');
  return '${time.year}-$month-$day-$hour$minute$second';
}

abstract final class WeChat {
  /// 如果发送失败，返回错误
  static Future<void> send({
    required Map<String, dynamic> data,
    List<String>? atMobiles,
    List<String>? atUserIds,
  }) async {
    if (WECHAT_WEBHOOK_URL.isEmpty || WECHAT_TOKEN.isEmpty) {
      return print('企业微信url 或者 key 为空');
    }
    if (noDing) return;

    print('是否发送企业微信消息（y/n，默认 y）：', ln: false);
    final input = stdin.readLineSync();
    if (input?.toLowerCase() == 'n') {
      return;
    }

    final resp = await Dio().post(
      WECHAT_WEBHOOK_URL,
      data: {
        // 'at': {
        //   'isAtAll': 'false',
        //   if (atUserIds != null) 'atUserIds': atUserIds,
        //   if (atMobiles != null) 'atMobiles': atMobiles,
        // },
        'markdown': {
          'content': data.toMarkdown(),
          'title': appName ?? '构建结果',
        },
        'msgtype': 'markdown',
      },
      queryParameters: {
        'timestamp': DateTime.now().millisecondsSinceEpoch,
        'key': WECHAT_TOKEN,
      },
      options: Options(
        headers: {
          'Content-Type': 'application/json',
        },
      ),
    );
    if (resp.statusCode != 200) {
      return print('发送失败: ${resp.statusCode}, ${resp.data['errmsg']}');
    }
    print('发送企业微信成功');
  }
}

void print(
  Object? obj, {
  /// 绿色
  String? color = '\x1b[32m',
  bool needVerbose = false,

  /// 换行
  bool ln = true,
}) {
  if (!verbose && needVerbose) return;

  /// 根据类型，以不同格式输出
  String str = '';
  switch (obj.runtimeType) {
    case const (String) || const (int) || const (double) || const (bool):
      str = obj.toString();
      if (color == null || str.contains('\n') || str.length > 10) {
        // pass
      } else {
        str = '$color$str\x1b[0m';
      }
      break;
    default:
      str = ENCODER.convert(obj);
  }
  str = ln ? '$str\n' : str;
  stdout.write(str);
}

extension MapX on Map<String, dynamic> {
  String toMarkdown() {
    final buf = StringBuffer();
    for (final entry in entries) {
      buf.write('### ${entry.key}\n');
      buf.write(entry.value);
      buf.write('\n');
    }
    return buf.toString();
  }
}

final class Profile {
  const Profile({
    required this.name,
    this.chan,
    this.appName,
  });

  final String? chan;
  final String? appName;
  final String name;

  /// 获取配置文件名称的正则
  static final nameReg = RegExp(r'<configuration (\S+ )?name="([^"]+)"');

  /// 获取配置文件中的参数的正则
  static final argsReg = RegExp(r'<option name="additionalArgs" value="([^"]+)"');

  /// 从本地读取配置文件
  static Future<List<Profile>?> fromLocal() async {
    final dir = Directory('.run');
    if (!await dir.exists()) {
      return null;
    }
    final files = await dir.list().toList();
    final profiles = <Profile>[];
    for (final file in files) {
      final profile = fromFile(File(file.path));
      if (profile != null) {
        profiles.add(profile);
      }
    }
    return profiles;
  }

  /// 从本地选择配置文件
  static Future<Profile?> selectFromLocal() async {
    final profiles = await Profile.fromLocal();
    if (profiles == null || profiles.isEmpty) {
      print('无配置文件');
      return null;
    }
    if (profiles.length == 1) {
      return profiles.first;
    }
    if (profileName != null) {
      for (final profile in profiles) {
        if (profile.name == profileName) {
          return profile;
        }
      }
      print('未找到配置: $profileName\n');
    }
    for (var i = 0; i < profiles.length; i++) {
      print('$i: ${profiles[i].name}');
    }
    print('选择配置文件（默认 0）：', color: null, ln: false);
    final index = int.tryParse(stdin.readLineSync() ?? '') ?? 0;
    if (index < 0 || index >= profiles.length) {
      throw ArgumentError('无效索引');
    }
    return profiles[index];
  }

  /// 从文件读取配置
  static Profile? fromFile(File file) {
    final content = file.readAsStringSync();
    final name = nameReg.firstMatch(content)?.group(2);
    final args = argsReg.firstMatch(content)?.group(1);
    if (name == null || args == null) {
      return null;
    }
    final argsMap = <String, String>{};
    for (final arg in args.split(' ')) {
      final kv = arg.split('=');
      if (kv.length != 3 || kv[0] != '--dart-define') {
        continue;
      }
      argsMap[kv[1]] = kv[2];
    }
    final chan = argsMap['APP_INSTALL_CHANNEL'];
    final appName = argsMap['APP_NAME'];
    return Profile(
      chan: chan,
      appName: appName,
      name: name,
    );
  }
}

/// 用于获取包名
abstract final class PkgInfo {
  /// 用于匹配包名的正则
  static final idReg = RegExp(r'applicationId\s+"([^"]+)"');

  /// 获取包名，例如：com.hyzh.park_v6
  static Future<String?> get pkgId async {
    final gradleFile = File('android/app/build.gradle');
    if (!await gradleFile.exists()) {
      return null;
    }
    final content = await gradleFile.readAsString();
    final match = idReg.firstMatch(content);
    return match?.group(1);
  }

  static final versionReg = RegExp(r'version: (\d+\.\d+\.\d+)');

  /// 获取版本号，例如：1.0.0
  static Future<String?> get version async {
    final pubspecFile = File('pubspec.yaml');
    if (!await pubspecFile.exists()) {
      return null;
    }
    final content = await pubspecFile.readAsString();
    return versionReg.firstMatch(content)?.group(1);
  }
}

/// 上传文件到蒲公英
class AppUploader {

  /// 先获取 COS Token
  /// 再上传文件
  /// 返回是否上传成功
  static Future<bool> getCOSToken({
    required String path,
    String? updateDescription,
    String? payload,
  }) async {
    try {
      final FormData formData = FormData.fromMap({
        '_api_key': '51703a1d7b85abc64a6727ab4a9d7812',
        'buildType': 'android', // 需要上传的应用类型，ios 或 android
        'buildInstallType': 1, // (选填)应用安装方式，值为(1,2,3，默认为1 公开安装)。1：公开安装，2：密码安装，3：邀请安装
        'buildUpdateDescription': updateDescription ?? '', // (选填) 版本更新描述，请传空字符串，或不传。
      });

      final Response<dynamic> response = await Dio(BaseOptions(
        baseUrl: 'https://www.pgyer.com/apiv2/app/',
        headers: {'Content-Type': 'multipart/form-data'},
      )).post('https://www.pgyer.com/apiv2/app/getCOSToken', data: formData);
      if (response.statusCode == 200) {
        final data = response.data as Map<String, dynamic>;
        print(data.toString());
        final uploadUrl = data['data']['endpoint'].toString();
        final payload = data['data']['params'] as Map<String, dynamic>;
        await _upload(uploadUrl, path, payload);
        return true;
      } else {
        return false;
      }
    } catch (e) {
      print('getCOSToken服务器暂时无法为您服务: $e');
      return false;
    }
  }


  // 上传文件的方法需要根据实际情况来实现
  static Future<void> _upload(String uploadUrl, String path, Map<String, dynamic> payload,) async {
    try {
      final FormData formData = FormData.fromMap({
        ...payload, // 从getCOSToken获取的参数
        'file': await MultipartFile.fromFile(path, filename: path),
      });

      final Response<dynamic> response = await Dio().post(uploadUrl, data: formData,onSendProgress: (int sent, int total) {
        print('上传进度: ${(sent / total * 100).toInt()}%');
      });

      if (response.statusCode == 204) {
        print('上传成功，正在获取包处理信息，请稍等...');
        await getBuildInfo('51703a1d7b85abc64a6727ab4a9d7812', payload['key'].toString());
      } else {
        print('HTTPError, Code: ${response.statusCode}');
      }
    } catch (e) {
      print('上传服务器暂时无法为您服务: $e');
    }
  }

  // 获取应用构建信息
  static Future<void> getBuildInfo(String apiKey, String buildKey,) async {
    try {
      final Response<dynamic> response = await Dio(BaseOptions(
        headers: {'Content-Type': 'multipart/form-data'},
      )).get('https://www.pgyer.com/apiv2/app/buildInfo',
          queryParameters: {
            '_api_key': apiKey,
            'buildKey': buildKey,
          },
      );

      if (response.statusCode == 200) {
        final result = response.data;
        final code = result['code'];

        if (code == 1247 || code == 1246) {
          // 如果应用正在处理中，则等待一段时间后重试
          await Future.delayed(const Duration(seconds: 3));
          getBuildInfo(apiKey, buildKey);
        } else {
          print('构建信息: $result');
          // getDownloadUrl(apiKey, buildKey,result['data']['buildShortcutUrl'].toString());
        }
      } else {
        print('获取构建信息失败, Code: ${response.statusCode}');
      }
    } catch (e) {
      print('获取构建信息失败: $e');
    }
  }

  // 获取下载地址 https://www.pgyer.com/apiv2/app/install
  static Future<void> getDownloadUrl(String apiKey, String buildKey,String? buildShortcutUrl) async {
    try {
      final Response<dynamic> response = await Dio().get('http://www.pgyer.com/apiv1/app/install',
          queryParameters: {
            '_api_key': apiKey,
            // 'buildKey': 'https://www.pgyer.com/AoUm8gft',
            'aKey': buildShortcutUrl,
          },
      );

      if (response.statusCode == 200) {
        final result = response.data;
        print('下载地址: ${result}');
      }
    } catch (e) {
      print('获取下载地址失败: $e');
    }
  }

}
